#include "./scan_uart.h"
#include "../xlib/dma.h"
#include "../xlib/gpio.h"
#include "../xlib/uart.h"


namespace driver {

ScanUart scanUart;

using namespace stm32f1;

namespace scan_uart {

using namespace gpio;

using TX = Cfg<PA<9>, Mode::OUT_50MHz_AF_PP>;
using RX = Cfg<PA<10>, Mode::INPUT_FLOAT>;
using UART = uart::Uart1;
using DMA_TX = dma::DmaSel<UART::BASE_VAL>::TX_CHAN;
using DMA_RX = dma::DmaSel<UART::BASE_VAL>::RX_CHAN;

struct ScanCfg : public uart::DefCfg {
    static constexpr uint32_t BAUD_RATE = 9600;
};

}

using namespace scan_uart;
using namespace utils;

const uint8_t SCAN_BYTES[] = { 0x1B, 0x31 };

void ScanUart::init() {
    m_mq.init();
    m_flags.init();

    rcc::enable<TX, RX, UART, DMA_TX, DMA_RX>();
    gpio::config<TX, RX>();
    UART::set_cfg<ScanCfg>();

    NVIC_EnableIRQ(USART1_IRQn);
    NVIC_EnableIRQ(DMA1_Channel4_IRQn);
}

void ScanUart::on_recv_idle() {
    uint32_t index = RECV_BUF_SIZE - DMA_RX::ndtr();
    if (m_recv_index == index) {
        return;
    }
    m_mq.post( utils::ByteView(m_recv_buf + m_recv_index, index - m_recv_index) );
    m_recv_index = index;
}

Error ScanUart::scan(ByteView &result) {
    m_recv_index = 0;
    DMA_RX::set_cfg<dma::UartRecvCfg, UART::DR_ADDR_VAL>(
        reinterpret_cast<uint32_t>(m_recv_buf),
        RECV_BUF_SIZE
    );
    m_mq.reset();
    DMA_TX::set_cfg<dma::UartWriteCfg, UART::DR_ADDR_VAL>(
        reinterpret_cast<uint32_t>(SCAN_BYTES),
        2
    );
    m_flags.wait_any_flags(FLAG_TC);

    if (osOK != m_mq.poll(result, 500)) {
        result.len = 0;
        return ec::SCAN_TIMEOUT;
    }

    if (osOK != m_mq.poll(result, 3000)) {
        result.len = 0;
        return ec::SCAN_TIMEOUT;
    }

    if (result.len < 2) {
        result.len = 0;
        return ec::SCAN_INVALID;
    }
    result.len -= 2;
    return ec::OK;
}

}

using namespace driver;

extern "C" void USART1_IRQHandler() {
    if (UART::is_idle()) {
        UART::clear_idle_flag();
        scanUart.on_recv_idle();
    }
}

extern "C" void DMA1_Channel4_IRQHandler() {
    if (DMA_TX::is_tc()) {
        DMA_TX::clear_flags();
        scanUart.on_dma_tc();
    }
}


